﻿/*
Copyright (c) 2007-2011 William POTTIER - AllProgrammic

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
of the Software, and to permit persons to whom the Software is furnished to do so, 
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all 
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NetAccess.Common;

namespace NetAccess.Server
{
    /// <summary>
    /// ServerConnexion layer with advanced message routing features
    /// </summary>
    /// <typeparam name="TServerLayer"></typeparam>
    /// <typeparam name="TMessageType"></typeparam>
    /// <typeparam name="TState"></typeparam>
    public class ExtendedServerConnexion<TServerLayer, TMessageType, TState> : ServerConnexion<TServerLayer>
        where TServerLayer : BaseServerLayer where TMessageType : class
    {
        #region Incomming message routing
        // Message handlers store
        private static Dictionary<TMessageType, HandlerInfo> _handlers;

        private struct HandlerInfo
        {
            public MethodInfo Method;
            public TState RequiredState;
            public int ArgsCount;
        }

        internal void InitializeMessageHandlers()
        {
            var handlers = new Dictionary<TMessageType, HandlerInfo>();

            // Charge les methodes cliente
            MethodInfo[] mis = GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            var iMax = mis.Length;
            for (var i = 0; i < iMax; i++)
            {
                // Cherche l'attribut
                var attribs = mis[i].GetCustomAttributes(typeof (MessageHandlerAttribute), true);

                if (attribs.Length != 1)
                    continue;

                // Mémorise l'attribut
                var attrib = (MessageHandlerAttribute) attribs[0];

                if (handlers.ContainsKey((TMessageType) attrib.MessageType))
                {
                    // Si il y a déjà un handler définis pour ce type de message.
                    throw new Exception(
                        String.Format(
                            "Le type de message {0} dispose déjà d'un handler. Impossible de définir un nouveau handler pour ce message.",
                            attrib.MessageType));
                }

                // Récupère le schéma du message
                var fi = typeof (TMessageType).GetField(attrib.MessageType.ToString());

                // On compare la signature de l'handler et le schéma du packet
                var msAttribs = fi.GetCustomAttributes(typeof (MessageStructAttribute), true);

                if (msAttribs.Length == 0)
                {
                    // Le packet n'a pas d'information de structure
                    throw new Exception(
                        String.Format(
                            "Le type de message {0} n'a pas d'information de structure. Impossible de définir un handler pour ce message.",
                            fi.Name));
                }

                // Structure du message
                var mstruct = (MessageStructAttribute) msAttribs[0];

                // Paramètres de l'handler
                var opts = mis[i].GetParameters();

                // Teste la compatibilité du message et de l'handler
                if (mstruct.Elements.Length != opts.Length || mstruct.Elements.Where((t, j) => t != opts[j].ParameterType).Any())
                {
                    throw new Exception(
                        String.Format(
                            "L'handler {0} pour les messages de type {1} ne dispose pas d'une signature valide! (type incorrect)",
                            mis[i].Name, attrib.MessageType));

                }

                handlers.Add((TMessageType) attrib.MessageType,
                             new HandlerInfo
                                 {
                                     Method = mis[i],
                                     RequiredState = (TState) attrib.RequiredState,
                                     ArgsCount = mstruct.Elements.Length
                                 });

            }

            _handlers = handlers;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Return the State of the connexion
        /// </summary>
        public TState State { get; protected set; }
        #endregion

        /// <summary>
        /// Call on initialization process
        /// </summary>
        protected override void OnInitialize()
        {
            if (_handlers == null)
            {
                InitializeMessageHandlers();
            }
        }

        protected override void DataReceived(Message.NetworkMessage message)
        {
            var mType = (TMessageType)message.MessageIdentifier;

            if (mType != null && _handlers.ContainsKey(mType))
            {
                HandlerInfo hInfo = _handlers[mType];

                if (!hInfo.RequiredState.Equals(State))
                {
                    NotAuthorizedMessage(message);
                    return;
                }

                if (message.MessageData.Length != hInfo.ArgsCount)
                {
                    Console.WriteLine("Reception d'un message mal formé (nombre d'éléments incorrect) en provenance de {0}", Id);
                    return;
                }

                var it = 0;
                var valid = true;

                var pInfos = hInfo.Method.GetParameters();

                foreach (var pi in pInfos)
                {
                    var type = pi.ParameterType;

                    if (!type.IsValueType) // On attend un type reference
                    {
                        if (message.MessageData[it] != null && !type.Equals(message.MessageData[it].GetType()))
                        {
                            valid = false;
                            break;
                        }
                    }
                    else if ((type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))) // On attend un type Nullable 
                    {
                        if (message.MessageData[it] != null)
                        {
                            Type t2 = Nullable.GetUnderlyingType(type);
                            if (!t2.Equals(message.MessageData[it].GetType()))
                            {
                                valid = false;
                                break;
                            }
                        }
                    }
                    else    // On attend un type valeur (non nullable)
                    {
                        if (message.MessageData[it] == null)
                        {
                            valid = false;
                            break;
                        }
                    }
                    it += 1;
                }

                if (valid)
                {
                    hInfo.Method.Invoke(this, message.MessageData);
                }
                else
                {
                    Logger.Instance.Error(String.Format("Reception d'un paquet mal formé en provenance de {0}", Id));
                    return;
                }
            }
            else
            {
                Logger.Instance.Error(String.Format("Reception d'un message non identifiable en provenance de {0}", Id));
                return;
            }
        }

        /// <summary>
        /// Lancé lorsque un message non authorisé arrive
        /// </summary>
        /// <param name="message"></param>
        protected virtual void NotAuthorizedMessage(Message.NetworkMessage message)
        {

        }
    }

    /// <summary>
    /// Attributs pour le marquage des handlers
    /// </summary>
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public class MessageHandlerAttribute : Attribute
    {
        /// <summary>
        /// Message identifier
        /// </summary>
        public object MessageType { get; private set; }

        /// <summary>
        /// Required State
        /// </summary>
        public object RequiredState { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="messageType"></param>
        public MessageHandlerAttribute(object messageType)
        {
            MessageType = messageType;
            RequiredState = null;
        }
    }

    /// <summary>
    /// Attributs pour le marquage des type de message
    /// </summary>
    [AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
    public class MessageStructAttribute : Attribute
    {
        /// <summary>
        /// Array of type of the message parameters
        /// </summary>
        public Type[] Elements { get; private set; }

        /// <summary>
        /// Specify if message need to be encrypted
        /// </summary>
        public Boolean Crypted { get; private set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="crypted"></param>
        /// <param name="elements"></param>
        public MessageStructAttribute(bool crypted, params Type[] elements)
        {
            Elements = elements;
            Crypted = crypted;
        }
    }
}
